/**
 * TaskDetector 属性测试
 * 
 * 使用 fast-check 进行属性测试，验证任务检测器的正确性
 */

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as fc from 'fast-check';
import { TaskDetector } from './TaskDetector';
import { GitCodeApiService } from './GitCodeApiService';
import type { ParsedChat, EmotionalTone, DetectedTask } from '../types';

// ============================================
// 生成器定义
// ============================================

/**
 * 生成有效的发送者名称
 */
const senderArbitrary = fc.string({ minLength: 1, maxLength: 15 })
  .filter(s => {
    const trimmed = s.trim();
    return trimmed.length > 0 && 
           !trimmed.includes(':') && 
           !trimmed.includes('：') &&
           !trimmed.includes('\n');
  })
  .map(s => s.trim());

/**
 * 生成不包含任务相关内容的消息
 * 避免使用可能被识别为任务的关键词
 */
const nonTaskContentArbitrary = fc.string({ minLength: 1, maxLength: 100 })
  .filter(s => {
    const trimmed = s.trim().toLowerCase();
    // 排除可能被识别为任务的关键词
    const taskKeywords = [
      '任务', '计划', '安排', '完成', '截止', '提醒',
      '需要', '必须', '应该', '要做', '待办', '事项',
      '明天', '下周', '今天', '周末', '月底',
      'task', 'todo', 'plan', 'deadline', 'schedule',
      '帮忙', '处理', '负责', '跟进', '确认'
    ];
    return trimmed.length > 0 && 
           !trimmed.includes('\n') &&
           !taskKeywords.some(kw => trimmed.includes(kw));
  })
  .map(s => s.trim());

/**
 * 生成情感基调
 */
const emotionalToneArbitrary: fc.Arbitrary<EmotionalTone> = fc.constantFrom(
  'happy', 'sad', 'comfort', 'funny', 'grateful', 'apologetic', 'neutral'
);

/**
 * 生成不包含任务的 ParsedChat 对象
 */
const nonTaskParsedChatArbitrary: fc.Arbitrary<ParsedChat> = fc.tuple(
  fc.array(senderArbitrary, { minLength: 2, maxLength: 5 }),
  fc.array(nonTaskContentArbitrary, { minLength: 2, maxLength: 10 }),
  emotionalToneArbitrary
).map(([senders, contents, emotionalTone]) => {
  const uniqueSenders = [...new Set(senders)];
  if (uniqueSenders.length < 2) {
    uniqueSenders.push(uniqueSenders[0] + '_other');
  }
  
  const messages = contents.map((content, i) => ({
    sender: uniqueSenders[i % uniqueSenders.length],
    content,
    isUser: i % uniqueSenders.length === 1
  }));
  
  return {
    messages,
    participants: uniqueSenders,
    emotionalTone
  };
});

/**
 * 生成空的 ParsedChat 对象
 */
const emptyParsedChatArbitrary: fc.Arbitrary<ParsedChat> = fc.constant({
  messages: [],
  participants: [],
  emotionalTone: 'neutral' as EmotionalTone
});

// ============================================
// 测试辅助函数
// ============================================

/**
 * 创建带有模拟 API 的 TaskDetector（返回空任务列表）
 */
function createMockedDetectorWithEmptyTasks(): TaskDetector {
  const mockApiService = new GitCodeApiService({ apiKey: 'test-key' });
  vi.spyOn(mockApiService, 'chat').mockResolvedValue('{"tasks": []}');
  return new TaskDetector(mockApiService);
}

/**
 * 创建带有模拟 API 的 TaskDetector（API 抛出错误）
 */
function createMockedDetectorWithError(): TaskDetector {
  const mockApiService = new GitCodeApiService({ apiKey: 'test-key' });
  vi.spyOn(mockApiService, 'chat').mockRejectedValue(new Error('API Error'));
  return new TaskDetector(mockApiService);
}

// ============================================
// Property 9: 空任务列表安全返回
// ============================================

describe('TaskDetector Property Tests', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  /**
   * **Feature: smart-chat-reply, Property 9: 空任务列表安全返回**
   * **Validates: Requirements 7.3**
   * 
   * 对于任意不包含任务相关内容的聊天记录，TaskDetector.detect() 应返回空数组而非抛出错误或返回 null
   */
  describe('Property 9: 空任务列表安全返回', () => {
    it('不包含任务的聊天记录应返回空数组', async () => {
      await fc.assert(
        fc.asyncProperty(
          nonTaskParsedChatArbitrary,
          async (chat) => {
            const detector = createMockedDetectorWithEmptyTasks();
            
            const tasks = await detector.detect(chat);
            
            // 验证返回的是数组
            expect(Array.isArray(tasks)).toBe(true);
            // 验证返回空数组
            expect(tasks.length).toBe(0);
            // 验证不是 null 或 undefined
            expect(tasks).not.toBeNull();
            expect(tasks).toBeDefined();
          }
        ),
        { numRuns: 100 }
      );
    });

    it('空聊天记录应返回空数组', async () => {
      await fc.assert(
        fc.asyncProperty(
          emptyParsedChatArbitrary,
          async (chat) => {
            const detector = createMockedDetectorWithEmptyTasks();
            
            const tasks = await detector.detect(chat);
            
            // 验证返回空数组
            expect(Array.isArray(tasks)).toBe(true);
            expect(tasks.length).toBe(0);
          }
        ),
        { numRuns: 50 }
      );
    });

    it('API 错误时应返回空数组而非抛出异常', async () => {
      await fc.assert(
        fc.asyncProperty(
          nonTaskParsedChatArbitrary,
          async (chat) => {
            const detector = createMockedDetectorWithError();
            
            // 不应抛出异常
            let tasks: DetectedTask[] | null = null;
            let error: Error | null = null;
            
            try {
              tasks = await detector.detect(chat);
            } catch (e) {
              error = e as Error;
            }
            
            // 验证没有抛出异常
            expect(error).toBeNull();
            // 验证返回空数组
            expect(Array.isArray(tasks)).toBe(true);
            expect(tasks!.length).toBe(0);
          }
        ),
        { numRuns: 50 }
      );
    });

    it('API 返回无效 JSON 时应返回空数组', async () => {
      await fc.assert(
        fc.asyncProperty(
          nonTaskParsedChatArbitrary,
          fc.string({ minLength: 1, maxLength: 100 }).filter(s => {
            // 生成无效的 JSON 字符串
            try {
              JSON.parse(s);
              return false;
            } catch {
              return true;
            }
          }),
          async (chat, invalidJson) => {
            const mockApiService = new GitCodeApiService({ apiKey: 'test-key' });
            vi.spyOn(mockApiService, 'chat').mockResolvedValue(invalidJson);
            const detector = new TaskDetector(mockApiService);
            
            const tasks = await detector.detect(chat);
            
            // 验证返回空数组
            expect(Array.isArray(tasks)).toBe(true);
            expect(tasks.length).toBe(0);
          }
        ),
        { numRuns: 50 }
      );
    });

    it('API 返回缺少 tasks 字段的 JSON 时应返回空数组', async () => {
      await fc.assert(
        fc.asyncProperty(
          nonTaskParsedChatArbitrary,
          async (chat) => {
            const mockApiService = new GitCodeApiService({ apiKey: 'test-key' });
            // 返回有效 JSON 但缺少 tasks 字段
            vi.spyOn(mockApiService, 'chat').mockResolvedValue('{"result": "no tasks"}');
            const detector = new TaskDetector(mockApiService);
            
            const tasks = await detector.detect(chat);
            
            // 验证返回空数组
            expect(Array.isArray(tasks)).toBe(true);
            expect(tasks.length).toBe(0);
          }
        ),
        { numRuns: 50 }
      );
    });
  });
});
